package handler

import (
	"fmt"
	"math"
	"strings"
	"time"

	ds "gitee.com/shipnet/flogo_edge_channel_data_store"
	ms "gitee.com/shipnet/flogo_metric_store"
	"github.com/project-flogo/core/support/log"
	"github.com/tidwall/gjson"
)

type EdgeChannel struct {
	params             EdgeChannelParams
	stopCh             chan bool
	ticker             *time.Ticker
	currentMetric      ms.MetricData
	currentMetricValid bool
	currentData        ds.EdgeChannelData
	currentDataValid   bool
	publisher          func(data ds.EdgeChannelData) error
	logger             log.Logger
	ms                 ms.MetricStore
}

func NewEdgeChannel(ctx Context, params EdgeChannelParams) (*EdgeChannel, error) {
	ticker := time.NewTicker(params.Interval)
	ch := &EdgeChannel{
		params:    params,
		stopCh:    make(chan bool),
		ticker:    ticker,
		publisher: ctx.publish,
		logger:    ctx.logger,
		ms:        ctx.metricStore,
	}
	return ch, nil
}

func (ch *EdgeChannel) Read() (ds.EdgeChannelData, error) {
	return ch.currentData, nil
}

func (ch *EdgeChannel) GetState() EdgeChannelState {
	var cm *ms.MetricData
	if ch.currentMetricValid {
		cm = &ch.currentMetric
	}
	var cd *ds.EdgeChannelData
	if ch.currentDataValid {
		cd = &ch.currentData
	}
	return EdgeChannelState{
		Params:              ch.params,
		LastMetricData:      cm,
		LastEdgeChannelData: cd,
	}
}

func (ch *EdgeChannel) Stop() error {
	ch.stopCh <- true
	ch.ticker.Stop()
	return nil
}

func (ch *EdgeChannel) UpdateParams(params EdgeChannelParams) error {
	ch.params = params
	return nil
}

func (ch *EdgeChannel) Run() {
	switch strings.ToUpper(ch.params.Mode) {
	case "INTERVAL":
		{
			ch.runInterval()
			return
		}
	case "ONCHANGE":
		{
			ch.runOnChange()
			return
		}
	default:
		{
			return
		}
	}
}

func (ch *EdgeChannel) runInterval() {
	for {
		select {
		case <-ch.stopCh:
			{
				return
			}
		case <-ch.ticker.C:
			{
				// read metric from metric store
				m, err := ch.ms.Get(ch.params.MetricDataID)
				if err != nil {
					ch.logger.Errorf("failed read metric data: %v", err)
					break
				}
				if m == nil {
					ch.logger.Warnf("metric data is missing: %s", ch.params.MetricDataID)
					break
				}
				ch.currentMetric = *m
				ch.currentMetricValid = true
				ed, err := ch.genEdgeChannelData(*m)
				if err != nil {
					ch.logger.Errorf("failed generate EdgeChannelData: %v", err)
					break
				}
				if ed != nil {
					err := ch.publisher(*ed)
					if err != nil {
						ch.logger.Errorf("failed output EdgeChannelData: %v", err)
					} else {
						ch.logger.Infof("output EdgeChannelData: %+v", *ed)
					}
					ch.currentData = *ed
					ch.currentDataValid = true
				}
			}
		}
	}
}

func (ch *EdgeChannel) runOnChange() {
	sub, err := ch.ms.Subscribe(ch.params.EdgeChannelID)
	if err != nil {
		return
	}
	for {
		select {
		case <-ch.stopCh:
			{
				return
			}
		case md := <-sub.Channel():
			{
				ed, err := ch.genEdgeChannelData(md)
				if err != nil {
					ch.logger.Errorf("failed generate EdgeChannelData: %v", err)
					break
				}
				if ed != nil {
					err := ch.publisher(*ed)
					if err != nil {
						ch.logger.Errorf("failed output EdgeChannelData: %v", err)
					} else {
						ch.logger.Infof("output EdgeChannelData: %+v", *ed)
					}
					ch.currentMetric = md
					ch.currentMetricValid = true
					ch.currentData = *ed
					ch.currentDataValid = true
				}
			}
		}
	}
}

func (ch *EdgeChannel) genEdgeChannelData(m ms.MetricData) (*ds.EdgeChannelData, error) {
	err := checkDataType(m.Value, ch.params.DataType)
	if err != nil {
		return nil, err
	}
	mode := strings.ToUpper(ch.params.Mode)
	switch mode {
	case "ONCHANGE":
		{
			if !checkDataDiff(ch.currentMetric.Value, m.Value, ch.params.MinThreshold) {
				return nil, nil
			} else {
				ed := ds.EdgeChannelData{
					ID:        ch.params.EdgeChannelID,
					Timestamp: m.Timestamp,
					Value:     m.Value,
				}
				return &ed, nil
			}
		}
	case "INTERVAL":
		{
			ed := ds.EdgeChannelData{
				ID:        ch.params.EdgeChannelID,
				Timestamp: m.Timestamp,
				Value:     m.Value,
			}
			return &ed, nil
		}
	default:
		{
			return nil, fmt.Errorf("invalid mode: %s", ch.params.Mode)
		}
	}
}

func checkDataType(value gjson.Result, dataType string) error {
	return nil
}

func checkDataDiff(prev gjson.Result, curr gjson.Result, threshold float64) bool {
	if prev.Type != curr.Type {
		return true
	}
	switch prev.Type {
	case gjson.String:
		{
			return prev.Str != curr.Str
		}
	case gjson.Number:
		{
			a := prev.Float()
			b := prev.Float()
			return math.Abs(a-b) >= threshold
		}
	case gjson.JSON:
		{
			return prev.String() != curr.String()
		}
	default:
		{
			return true
		}
	}
}
